﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks.Dataflow;

namespace QuantBox.XApi
{
    internal class DealProcessor
    {
        #region Entrust Events
        private static class OrderEventType
        {
            public const byte NewOrder = 0;
            public const byte OrderCancel = 1;
            public const byte OrderRsp = 2;
            public const byte OrderRtn = 3;
        }
        private abstract class OrderEvent
        {
            public abstract byte TypeId { get; }
        }
        private class OrderRspEvent : OrderEvent
        {
            public override byte TypeId => OrderEventType.OrderRsp;
            public readonly Ufx91004Result Response;
            public OrderRspEvent(Ufx91004Result result)
            {
                Response = result;
            }
        }
        private class NewOrderEvent : OrderEvent
        {
            public readonly OrderField Entrust;

            public NewOrderEvent(OrderField entrust)
            {
                Entrust = entrust;
            }

            public override byte TypeId => OrderEventType.NewOrder;
        }
        private class OrderRtnEvent : OrderEvent
        {
            public readonly object Data;
            public readonly sbyte MsgType;

            public OrderRtnEvent(sbyte msgType, object data)
            {
                MsgType = msgType;
                Data = data;
            }
            public override byte TypeId => OrderEventType.OrderRtn;
        }
        private class OrderCancelEvent : OrderEvent
        {
            public readonly string OrderId;

            public OrderCancelEvent(string orderId)
            {
                OrderId = orderId;
            }

            public override byte TypeId => OrderEventType.OrderCancel;
        }
        #endregion

        private readonly UfxClient _client;
        private readonly OrderMap _orders = new OrderMap();
        private readonly ActionBlock<OrderEvent> _action;
        private readonly HashSet<string> _cancelPending = new HashSet<string>();

        private void ProcessNewOrder(NewOrderEvent e)
        {
            var account = _client.AccountInfo!;
            var entrust = e.Entrust;
            _orders.AddOrder(entrust);
            if (entrust.Status != OrderStatus.NotSent)
            {
                _client.logger.Warn($"重复发送,id:{e.Entrust.ID},ref:{e.Entrust.LocalID}");
                return;
            }
            _client.logger.Debug($"订单发送,id:{e.Entrust.ID},ref:{e.Entrust.LocalID}");
            var function = UfxFuturesFunction
                .Make91004(account.UserToken,
                    UfxConvert.GetMarketNo(entrust.ExchangeID),
                    entrust.InstrumentID,
                    UfxConvert.GetEntrustDirection(entrust.Side),
                    UfxConvert.GetFutureDirection(entrust.OpenClose),
                    UfxConvert.GetUfxPriceType(entrust.Type),
                    entrust.Price,
                    (int)entrust.Qty)
                .SetAccountCode(account.AccountCode)
                .SetAssetNo(account.AssetNo)
                .SetCombiNo(account.CombinationNo)
                .SetInvestType(UfxInvestType.V6)
                .SetExtsystemId(int.Parse(entrust.LocalID))
                .SetThirdReff(entrust.ID);
            _client.AsyncCall(function);
        }

        private void OrderAction(OrderEvent e)
        {
            try
            {
                switch (e.TypeId)
                {
                    case OrderEventType.NewOrder:
                        ProcessNewOrder((NewOrderEvent)e);
                        break;
                    case OrderEventType.OrderRsp:
                        ProcessOrderRsp((OrderRspEvent)e);
                        break;
                    case OrderEventType.OrderRtn:
                        ProcessOrderRtn((OrderRtnEvent)e);
                        break;
                    case OrderEventType.OrderCancel:
                        ProcessOrderCancel((OrderCancelEvent)e);
                        break;
                }
            }
            catch (Exception ex)
            {
                _client.logger.Error(ex);
            }
        }

        private void ProcessOrderCancel(OrderCancelEvent e)
        {
            ProcessOrderCancel(e.OrderId);
        }

        private void ProcessOrderCancel(string orderId)
        {
            var order = _orders.Get(orderId);
            if (order == null)
            {
                _client.logger.Warn($"无效的订单ID,id:{orderId}");
                return;
            }
            if (string.IsNullOrEmpty(order.OrderID))
            {
                _client.logger.Debug($"撤单缓存,id:{orderId}");
                _cancelPending.Add(orderId);
                return;
            }

            _client.logger.Debug($"订单撤销,id:{orderId},ref:{order.LocalID},oid:{order.OrderID}");
            var function = UfxFuturesFunction.Make91105(_client.AccountInfo!.UserToken, int.Parse(order.OrderID));
            _client.AsyncCall(function);
        }

        private void ProcessCancelPending(string orderId)
        {
            if (_cancelPending.Remove(orderId))
            {
                ProcessOrderCancel(orderId);
            }
        }

        private void ProcessOrderRtn(OrderRtnEvent e)
        {
            switch (e.MsgType)
            {
                case UfxMessageType.EntrustCancelled:
                    ProcessCancelled((UfxEntrustCancelled)e.Data);
                    break;
                case UfxMessageType.EntrustConfirmed:
                    ProcessConfirmed((UfxEntrustConfirmed)e.Data);
                    break;
                case UfxMessageType.EntrustReceived:
                    ProcessReceived((UfxEntrustReceived)e.Data);
                    break;
                case UfxMessageType.EntrustRejected:
                    ProcessRejected((UfxEntrustRejected)e.Data);
                    break;
                case UfxMessageType.EntrustTrade:
                    ProcessTrade((UfxEntrustTrade)e.Data);
                    break;
            }
        }

        private void ProcessTrade(UfxEntrustTrade data)
        {
            var order = _orders.Get(IdGenerator.GetIdFromThirdRef(data.ThirdReff));
            if (order == null)
            {
                _client.logger.Debug($"外部成交,ref:{data.ThirdReff},oid:{data.EntrustNo},tid:{data.DealNo}");
                return;
            }
            _client.logger.Debug($"新成交,id:{order.ID},oid:{data.EntrustNo},tid:{data.DealNo}");
            var trade = new TradeField();
            trade.ID = order.ID;
            trade.InstrumentID = data.StockCode;
            trade.ExchangeID = UfxConvert.GetExchange(data.MarketNo);
            trade.AccountID = data.AccountCode;
            trade.ClientID = order.ClientID;
            trade.Side = order.Side;
            trade.OpenClose = UfxConvert.GetTradeOpenClose(data.CloseType);
            trade.Commission = data.DealFee;
            trade.Price = data.DealPrice;
            trade.Qty = data.DealAmount;
            trade.TradeID = data.DealNo;
            trade.Date = data.DealDate;
            trade.Time = data.DealTime;
            trade.ReserveInt32 = data.EntrustNo;
            _client.Spi.ProcessRtnTrade(trade);
        }

        private void ProcessRejected(UfxEntrustRejected data)
        {
            var order = _orders.Get(IdGenerator.GetIdFromThirdRef(data.ThirdReff));
            if (order == null)
            {
                _client.logger.Debug($"外部无效订单,ref:{data.ThirdReff},oid:{data.EntrustNo},{data.RevokeCause}");
                return;
            }
            _client.logger.Debug($"无效订单,id:{order.ID},ref:{data.ThirdReff},oid:{data.EntrustNo},{data.RevokeCause}");
            _cancelPending.Remove(order.ID);
            ReportOrder(order, ExecType.Rejected, OrderStatus.Rejected, -1, -1, data.RevokeCause);
        }

        private void ProcessReceived(UfxEntrustReceived data)
        {
            var order = _orders.Get(IdGenerator.GetIdFromThirdRef(data.ThirdReff));
            if (order == null)
            {
                _client.logger.Debug($"外部订单,ref:{data.ThirdReff},oid:{data.EntrustNo}");
                return;
            }
            order.OrderID = data.EntrustNo.ToString();
            order.Date = data.BusinessDate;
            order.Time = data.BusinessTime;
            _client.logger.Debug($"订单回报,id:{order.ID},oid:{order.OrderID},{OrderStatus.New}");
            ReportOrder(order, ExecType.New, OrderStatus.New);
            ProcessCancelPending(order.ID);
        }

        private void ProcessConfirmed(UfxEntrustConfirmed data)
        {
            var order = _orders.Get(IdGenerator.GetIdFromThirdRef(data.ThirdReff));
            if (order == null)
            {
                _client.logger.Debug($"外部订单确认,ref:{data.ThirdReff},oid:{data.EntrustNo}");
                return;
            }
            order.OrderID = data.EntrustNo.ToString();
            order.Date = data.BusinessDate;
            order.Time = data.BusinessTime;
            _client.logger.Debug($"订单确认,id:{order.ID},oid:{order.OrderID},{OrderStatus.New}");
            ReportOrder(order, ExecType.New, OrderStatus.New);
            ProcessCancelPending(order.ID);
        }

        private void ProcessCancelled(UfxEntrustCancelled data)
        {
            var order = _orders.Get(IdGenerator.GetIdFromThirdRef(data.ThirdReff));
            if (order == null)
            {
                _client.logger.Debug($"外部撤单完成,ref:{data.ThirdReff},oid:{data.EntrustNo}");
                return;
            }
            order.Date = data.BusinessDate;
            order.Time = data.BusinessTime;
            _client.logger.Debug($"撤单完成,id:{order.ID},oid:{order.OrderID}");
            ReportOrder(order, ExecType.Cancelled, OrderStatus.Cancelled);
        }

        private void ProcessOrderRsp(OrderRspEvent e)
        {
            var rsp = e.Response;
            var order = _orders.GetByLocalId(rsp.ExtsystemId);
            if (order == null)
            {
                _client.logger.Debug($"外部风控拦截,ref:{rsp.ExtsystemId},oid:{rsp.EntrustNo},{rsp.FailCause}");
                return;
            }

            order.OrderID = rsp.EntrustNo.ToString();
            if (rsp.EntrustFailCode != UfxEntrustFailCode.V1)
            {
                _cancelPending.Remove(order.ID);
                _client.logger.Debug($"风控拦截,id:{order.ID},ref:{order.LocalID},oid:{order.OrderID},{rsp.FailCause}");
                ReportOrder(order, ExecType.Rejected, OrderStatus.Rejected, rsp.EntrustFailCode, rsp.RiskSerialNo, rsp.FailCause);
            }
        }

        private void ReportOrder(OrderField order, ExecType execType, OrderStatus newStatus, int errorId = 0, int rawErrorId = 0, string text = "")
        {
            order.Status = newStatus;
            var report = order.Clone();
            report.ExecType = execType;
            if (!string.IsNullOrEmpty(text))
            {
                report.XErrorID = errorId;
                report.RawErrorID = rawErrorId;
                report.SetText(text);
            }
            _client.Spi.ProcessRtnOrder(report);
        }

        public DealProcessor(UfxClient client)
        {
            _client = client;
            _action = new ActionBlock<OrderEvent>(OrderAction);
        }

        public string SendOrder(OrderField order)
        {
            _action.Post(new NewOrderEvent(order));
            return order.ID;
        }

        public void CancelOrder(string id)
        {
            _action.Post(new OrderCancelEvent(id));
        }

        public void PostOrderRsp(Ufx91004Result result)
        {
            _action.Post(new OrderRspEvent(result));
        }

        public void PostOrderRtn(sbyte msgType, object result)
        {
            _action.Post(new OrderRtnEvent(msgType, result));
        }

        public void Close()
        {
            _action.Complete();
            _action.Completion.Wait();
        }
    }
}